﻿using System;
using System.Net;
using System.Net.Http;
using System.Text;
using Identification.Importer.Core.Entity;
using Identification.Importer.Core.Setting;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Polly;

namespace Identification.Importer.Core.Job
{
    public class BaseProcessor<TSource, TTarget>
        where TSource : class
        where TTarget : BaseModel
    {
        protected ILogger Logger;

        protected DateTime Timestamp;

        protected readonly IMongoCollection<TTarget> TargetCollection;

        protected readonly IMongoCollection<TSource> SourceCollection;

        protected const string Error = "500";

        static readonly HttpClient _client;

        readonly SettingOption _option;

        static BaseProcessor()
        {
            _client = new HttpClient();
            _client.DefaultRequestHeaders.Connection.Add("keep-alive");
        }

        protected BaseProcessor(SettingOption option)
        {
            _option = option;

            var sourceMongoClient = new MongoClient(_option.SourceMongoServer);
            var sourceMongoDatabase = sourceMongoClient.GetDatabase(option.SourceMongoDatabase);
            SourceCollection = sourceMongoDatabase.GetCollection<TSource>(option.SourceMongoCollection);

            var targetMongoClient = new MongoClient(_option.TargetMongoServer);
            var targetMongoDatabase = targetMongoClient.GetDatabase(option.TargetMongoDatabase);
            TargetCollection = targetMongoDatabase.GetCollection<TTarget>(option.TargetMongoCollection);
        }

        /// <summary>
        /// 通过email和mobile获取globalId
        /// </summary>
        /// <param name="email"></param>
        /// <param name="mobile"></param>
        /// <returns></returns>
        protected string FetchGlobalId(string email, string mobile)
        {
            var data = new
            {
                _option.ProductName,
                Email = email,
                Mobile = mobile,
                UVId = default(string),
                UnionId = default(string)
            };

            var fallbackPolicy = Policy<HttpResponseMessage>.Handle<Exception>()
                .Fallback(() => new HttpResponseMessage(HttpStatusCode.InternalServerError));

            var retryPolicy = Policy.Handle<Exception>()
                .OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
                .WaitAndRetry(5, ts => TimeSpan.FromSeconds(Math.Pow(2, ts)),
                    (rs, ts, retry, ctx) =>
                    {
                        if (retry >= 2)
                        {
                            if (rs.Exception != null)
                            {
                                Logger.LogError("{Exception}", rs.Exception);
                            }

                            if (rs.Result != null)
                            {
                                Logger.LogError("{HttpResponseMessage}", rs.Result);
                            }
                        }
                    });

            var result = fallbackPolicy.Wrap(retryPolicy).Execute(() =>
            {
                var baseUri = new Uri(_option.IdentityWebServer);
                var fullUri = new Uri(baseUri, _option.IdentityWebSource);
                using (var request = new HttpRequestMessage(HttpMethod.Post, fullUri))
                {
                    var postData = JsonConvert.SerializeObject(data);
                    using (var content = new StringContent(postData, Encoding.UTF8, "application/json"))
                    {
                        request.Content = content;
                        return _client.SendAsync(request).Result;
                    }
                }
            });

            return result.IsSuccessStatusCode ? result.Content.ReadAsStringAsync().Result : Error;
        }

        protected Policy RetryWorker
        {
            get
            {
                var policy = Policy.Handle<Exception>().WaitAndRetry(5,
                    ts => TimeSpan.FromSeconds(Math.Pow(2, ts)),
                    (ex, ts, retry, ctx) =>
                    {
                        Logger.LogError("setting:{setting},exception:{exception}", _option, ex);

                        var delFilter = Builders<TTarget>.Filter.Eq(t => t.CreateTime, this.Timestamp);
                        TargetCollection.DeleteMany(delFilter);

                        if (retry >= 5 && !string.IsNullOrWhiteSpace((_option.SourceLatestObjectId)))
                        {
                            var projection = new BsonDocument {{"_id", 1}};
                            var objectId = ObjectId.Parse(_option.SourceLatestObjectId);
                            var idFilter = Builders<TSource>.Filter.Gt("_id", objectId);
                            var nextOne = SourceCollection.Find(idFilter).Project(projection).Limit(1).FirstOrDefault();

                            if (nextOne != null && nextOne.TryGetValue("_id", out var val))
                            {
                                _option.SourceLatestObjectId = (val ?? "").ToString();
                            }
                        }
                    });

                return policy;
            }
        }
    }
}